package com.zm.spring.demo;

import com.zm.spring.aop.AopConfig;
import com.zm.spring.demo.biz.IUserBiz;
import com.zm.spring.demo.biz.UserBiz;
import com.zm.spring.demo.mapper.UserMapper;
import com.zm.spring.demo.web.UserController;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.SourceLocation;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.io.UnsupportedEncodingException;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class AopApplicationContext extends MyAnnotaltionConfigApplicationContext{
    List<AspectInfo> aspectInfoList = new ArrayList<>();

    Map<String,Object> targetBeanMap = new HashMap<>();

    Map<String,Object> proxyBeanMap = new HashMap<>();
    AopApplicationContext(Class configuration) throws UnsupportedEncodingException, ClassNotFoundException, InvocationTargetException, IllegalAccessException, InstantiationException {
        super(configuration);
        //提取切点 + 增强
        beanDefinitoinMap.forEach((name,beanDefinitoin)->{
            Object beanClass = beanDefinitoin.getBeanClass();
            Class cls = (Class) beanClass;

            Annotation aspect = cls.getAnnotation(Aspect.class);
            if (aspect!=null){
                //找到对应的bean 不考虑懒加载
                Object aspectBean = singletonObject.get(name);
                for (Method declaredMethod : cls.getDeclaredMethods()) {
                    Before before = declaredMethod.getAnnotation(Before.class);
                    After after = declaredMethod.getAnnotation(After.class);
                    AfterReturning afterReturning = declaredMethod.getAnnotation(AfterReturning.class);
                    AfterThrowing afterThrowing = declaredMethod.getAnnotation(AfterThrowing.class);
                    Around aound = declaredMethod.getAnnotation(Around.class);
                    if (before!=null){
                        String pointcut = before.value();
                        if(pointcut.matches("\\w+\\(\\s*\\)")){
                            //引用切点方法
                            String pointcutMethodName = pointcut.replaceAll("(\\w+)\\(\\s*\\)", "$1");
                            try {
                                Method method = cls.getDeclaredMethod(pointcutMethodName);
                                pointcut = method.getAnnotation(Pointcut.class).value();
                            } catch (NoSuchMethodException e) {
                                throw new RuntimeException(e);
                            }
                        }
                        AspectInfo ai = new AspectInfo("before",aspectBean,declaredMethod,pointcut);
                        aspectInfoList.add(ai);
                    }

                    if (after!=null){
                        String pointcut = after.value();
                        //TODO 任何提取切点表达式
                        if(pointcut.matches("\\w+\\(\\s*\\)")){
                            //引用切点方法
                            String pointcutMethodName = pointcut.replaceAll("(\\w+)\\(\\s*\\)", "$1");
                            try {
                                Method method = cls.getDeclaredMethod(pointcutMethodName);
                                pointcut = method.getAnnotation(Pointcut.class).value();
                            } catch (NoSuchMethodException e) {
                                throw new RuntimeException(e);
                            }
                        }
                        AspectInfo ai = new AspectInfo("after",aspectBean,declaredMethod,pointcut);
                        aspectInfoList.add(ai);
                    }

                    if (afterReturning!=null){
                        String pointcut = afterReturning.value();
                        //TODO 任何提取切点表达式
                        if(pointcut.matches("\\w+\\(\\s*\\)")){
                            //引用切点方法
                            String pointcutMethodName = pointcut.replaceAll("(\\w+)\\(\\s*\\)", "$1");
                            try {
                                Method method = cls.getDeclaredMethod(pointcutMethodName);
                                pointcut = method.getAnnotation(Pointcut.class).value();
                            } catch (NoSuchMethodException e) {
                                throw new RuntimeException(e);
                            }
                        }
                        AspectInfo ai = new AspectInfo("afterReturning",aspectBean,declaredMethod,pointcut);
                        aspectInfoList.add(ai);
                    }

                    if (afterThrowing!=null){
                        String pointcut = afterThrowing.value();
                        //TODO 任何提取切点表达式
                        if(pointcut.matches("\\w+\\(\\s*\\)")){
                            //引用切点方法
                            String pointcutMethodName = pointcut.replaceAll("(\\w+)\\(\\s*\\)", "$1");
                            try {
                                Method method = cls.getDeclaredMethod(pointcutMethodName);
                                pointcut = method.getAnnotation(Pointcut.class).value();
                            } catch (NoSuchMethodException e) {
                                throw new RuntimeException(e);
                            }
                        }
                        AspectInfo ai = new AspectInfo("afterThrowing",aspectBean,declaredMethod,pointcut);
                        aspectInfoList.add(ai);
                    }

                    if (aound!=null){
                        String pointcut = aound.value();
                        //TODO 任何提取切点表达式
                        if(pointcut.matches("\\w+\\(\\s*\\)")){
                            //引用切点方法
                            String pointcutMethodName = pointcut.replaceAll("(\\w+)\\(\\s*\\)", "$1");
                            try {
                                Method method = cls.getDeclaredMethod(pointcutMethodName);
                                pointcut = method.getAnnotation(Pointcut.class).value();
                            } catch (NoSuchMethodException e) {
                                throw new RuntimeException(e);
                            }
                        }
                        AspectInfo ai = new AspectInfo("aound",aspectBean,declaredMethod,pointcut);
                        aspectInfoList.add(ai);
                    }
                }
            }
        });



        //扫描所有的bean，用切点方法，收集被切到bean （忽略懒加载）
        singletonObject.forEach((name,baen)->{
            for (Method declaredMethod : baen.getClass().getDeclaredMethods()) {
                //使用切点判断方法是否被切
                for (AspectInfo aspectInfo : aspectInfoList) {
                    //execution(* com.zm.spring.demo..*.find*(..))
                    String regExp = aspectInfo.pointcut.replaceAll("execution\\((.+)\\)", "$1");
                    regExp = regExp.replaceAll("\\.{2}|\\*",".*");
                    if (declaredMethod.toString().matches(regExp)){
//                        System.out.println("regExp = " + regExp);
//                        System.out.println("declaredMethod = " + declaredMethod);
                        targetBeanMap.put(name,baen);
                        return;
                    }
                }
            }
        });
        System.out.println("-----------------目标对象------------------");
        targetBeanMap.values().forEach(System.out::println);
        System.out.println("-----------------切面对象------------------");
        aspectInfoList.forEach(System.out::println);

        //生成代理对象
        for(Map.Entry<String,Object> entry : targetBeanMap.entrySet()){
            String name = entry.getKey();
            Object bean = entry.getValue();
            //JDK or CGLIB
            if(bean.getClass().getInterfaces().length == 0){
                //CGLIB
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(bean.getClass());
                enhancer.setCallback((MethodInterceptor)(proxy,method, args, methodProxy)->{
                    JoinPoint joinPoint = createJoinPoint(method, args, bean);
                    try {
                        //前置增强
                        List<AspectInfo> befores = getAdvice("before", method);
                        for (AspectInfo aspectInfo : befores) {
                            aspectInfo.adviceMethod.invoke(aspectInfo.aspectBean, joinPoint);
                        }
                        Object ret = method.invoke(bean,args);
                        //返回增强
                        List<AspectInfo> afterReturnings = getAdvice("afterReturning", method);
                        for (AspectInfo aspectInfo : afterReturnings) {
                            aspectInfo.adviceMethod.invoke(aspectInfo.aspectBean, joinPoint);
                        }
                        return ret;
                    } catch(Exception e) {
                        //异常增强
                        List<AspectInfo> afterThrowings = getAdvice("afterThrowing", method);
                        for (AspectInfo aspectInfo : afterThrowings) {
                            aspectInfo.adviceMethod.invoke(aspectInfo.aspectBean, joinPoint);
                        }
                        throw e;
                    }finally {
                        //后置增强
                        List<AspectInfo> afters = getAdvice("after", method);
                        for (AspectInfo aspectInfo : afters) {
                            aspectInfo.adviceMethod.invoke(aspectInfo.aspectBean, joinPoint);
                        }
                    }
                });
                Object proxyBean = enhancer.create();
                proxyBeanMap.put(name,proxyBean);
            }else {
                //JDK
                Object proxyBean = Proxy.newProxyInstance(bean.getClass().getClassLoader(),
                        bean.getClass().getInterfaces(),
                        (proxy,method,args)->{
                            JoinPoint joinPoint = createJoinPoint(method, args, bean);
                            try {
                                //前置增强
                                List<AspectInfo> befores = getAdvice("before", method);
                                for (AspectInfo aspectInfo : befores) {
                                    aspectInfo.adviceMethod.invoke(aspectInfo.aspectBean, joinPoint);
                                }
                                Object ret = method.invoke(bean,args);
                                //返回增强
                                List<AspectInfo> afterReturnings = getAdvice("afterReturning", method);
                                for (AspectInfo aspectInfo : afterReturnings) {
                                    aspectInfo.adviceMethod.invoke(aspectInfo.aspectBean, joinPoint);
                                }
                                return ret;
                            } catch(Exception e) {
                                //异常增强
                                List<AspectInfo> afterThrowings = getAdvice("afterThrowing", method);
                                for (AspectInfo aspectInfo : afterThrowings) {
                                    aspectInfo.adviceMethod.invoke(aspectInfo.aspectBean, joinPoint);
                                }
                                throw e;
                            }finally {
                                //后置增强
                                List<AspectInfo> afters = getAdvice("after", method);
                                for (AspectInfo aspectInfo : afters) {
                                    aspectInfo.adviceMethod.invoke(aspectInfo.aspectBean, joinPoint);
                                }
                            }
                        }
                );
                proxyBeanMap.put(name,proxyBean);
            }
        }
        System.out.println("-----------------代理对象------------------");
        proxyBeanMap.values().forEach(System.out::println);
        //使用代理对象替换容器中的bean
        proxyBeanMap.forEach((name,proxyBean)->{
            singletonObject.put(name,proxyBean);
        });
    }
    //根据类型返回指定的切面
    public List<AspectInfo> getAdvice(String type,Method method){
        List<AspectInfo> list = aspectInfoList.stream().filter(aspectInfo -> {
            if (aspectInfo.type.equals(type) == false) {
                return false;
            }
            String regExp = aspectInfo.pointcut.replaceAll("execution\\((.+)\\)", "$1");
            regExp = regExp.replaceAll("\\.{2}|\\*", ".*");
            if (method.toString().matches(regExp)) {
                return true;
            }
            return false;
        }).collect(Collectors.toList());
        return list;
    }

    public JoinPoint createJoinPoint(Method method,Object args[],Object target){
        return new JoinPoint() {
            @Override
            public String toShortString() {
                return method.getName();
            }

            @Override
            public String toLongString() {
                return method.toString();
            }

            @Override
            public Object getThis() {
                return null;
            }

            @Override
            public Object getTarget() {
                return target;
            }

            @Override
            public Object[] getArgs() {
                return args;
            }

            @Override
            public Signature getSignature() {
                return null;
            }

            @Override
            public SourceLocation getSourceLocation() {
                return null;
            }

            @Override
            public String getKind() {
                return null;
            }

            @Override
            public StaticPart getStaticPart() {
                return null;
            }
        };
    }

    public static void main(String[] args) throws UnsupportedEncodingException, ClassNotFoundException, InvocationTargetException, IllegalAccessException, InstantiationException {
        AopApplicationContext cxt = new AopApplicationContext(AopConfig.class);
        System.out.println("************************************************");
        IUserBiz userBiz = cxt.getBean(UserBiz.class);
        System.out.println("##############-findByObject--------------");
        userBiz.findByObject(null);
        System.out.println("##############login--------------");
        userBiz.login("sdsa","ascasc");
        System.out.println("#################################################################");
        UserController userController = cxt.getBean(UserController.class);
        System.out.println("##############-findById--------------");
        userController.findById(100);
        System.out.println("##############-deleteById--------------");
        userController.deleteById(100);
        System.out.println("#################################################################");
        UserMapper userMapper = cxt.getBean(UserMapper.class);
        userMapper.findA();
    }
}
@Data
@AllArgsConstructor
@NoArgsConstructor
class AspectInfo{
    String type;
    Object aspectBean;
    Method adviceMethod;
    String pointcut;
}